/*
 *  AObjDoc.h
 *  
 *
 *  Created by Peter on 23.02.12.
 *  Copyright 2012 __MyCompanyName__. All rights reserved.
 *
 */
#ifndef A_OBJ_DOC_H
#define A_OBJ_DOC_H

#include "AObject.h"
#include "TrCompat.h"

#include <vector>
#include <typeinfo>

/*
 * This is basically a list of objects. AObjDoc objects are commonly stored
 * in bin, project and settings files.
 *
 * Note that Avid doesn't put this into their PClass Registry.
 *
 * FIXME: Do we want to destroy all the objects when the AObject is destroyed?
 */
class AObjDoc : public AObject
{
	
public:
	
	A_DECLARE_CLASS( 'OBJD', AObjDoc, AObject );
	
	// ----- enums & typedefs -----
	
	// Reference value used in files to reference other objects. This is used 
	// to (de-)serialize pointers.
	typedef UInt32 ObjectRef;
	
	// ----- member functions -----
		
	AObjDoc();
	
	// The readFromStream version taking another AObjDoc is only there to match
	// the signature in AObject. In theory, it would only be called if you 
	// stored an AObjDoc inside another AObjDoc for some reason.
	virtual void readFromStream( AStream& str, const AObjDoc& objd );
	virtual void writeToStream( AStream& str ) const;
	
	virtual void dump( std::ostream& str );
	
	void readFromStream( AStream& str );
	
	// Remove all objects from the document and reset everything
	void clear();
	
	// Returns the object with the given index. The return value is guaranteed 
	// to be non-null for non-null indices. If the index is out of range, the 
	// function throws. Since the index is one-based, 0 gives you NULL:
	// TODO: Add an overloaded operator []
	AObject* getObjectByIdx( unsigned int index ) const;
	
	// Returns the root object of the document. Guaranteed to return non-NULL.
	AObject* getRootObject() const;
	
	// Returns the object that corresponds to the given reference value in a 
	// file (internally, this is an index, but the idea is to abstract that 
	// concept away). This is guaranteed to return a non-null pointer unless the
	// reference value is null. If the reference cannot be resolved, the 
	// function throws.
	// Since we internally store AObject*s, the function dynamic_casts to the
	// given type and throws if that cast fails (i.e. if the referenced object
	// is not of the type that is expected.)
	template< class T >
	T* resolveRef( ObjectRef ref ) const;
	
	// Calling this function on an object will invoke its writeToStream() 
	// function, which then in turn should start by calling putToStream() for 
	// all the objects it references via pointer data members. That way we 
	// implement a depth-first approach that ensures that when reading an 
	// object, all the objects it references are already available.
	void putToStream( AObject& obj );
	
protected:
	
	// ----- enums & typedefs -----
	
	typedef std::vector< AObject* > ObjectList;
	
	
	// ----- data members -----
	
	// TODO: Update based on info in console logs
	std::string   _lastSavedStr;   // Last saved date as a string
	UInt32        _numObjects;     // Total number of objects
	UInt32        _rootObject;     // Index of the root object
	UInt32        _lastSaved;      // Last saved as a timestamp (part of a UUID I think)
	AObject::PCID _creator;        // FOURCC for the creator application ('ATve' for MC)
	AObject::PCID _fileType;       // FOURCC that describes the type of document ('ATob')
	std::string   _creatorVersion; // Name and version of the creator application as a string
	
	ObjectList _objects;

#ifdef DEBUG
	// Keep track of how often each of the objects in the document was claimed
	// via resolveRef(). That way we can tell if we missed an object during the
	// serialization process.
	mutable std::vector< int > _numTimesClaimed;
#endif
	
};

template< class T >
T* AObjDoc::resolveRef( AObjDoc::ObjectRef ref ) const
{
	TR_ASSERT( ref >= 0 && ref <= _numObjects );
	
	if( ref == 0 )
		return nullptr;
	
#ifdef DEBUG
	// In debug builds, we keep track of how often each object was claimed by
	// a call to this function.
	++_numTimesClaimed[ ref ];
#endif
	
	// This is guaranteed to be non-null
	AObject* obj = getObjectByIdx( ref );
	
	// getObjectByIdx should never return NULL
	TR_CHECK( obj );
	
	T* val = dynamic_cast< T* >( obj );
	
	if( !val )
	{
		TR_TRACE (
			"Trying to resolve reference to object of wrong type in AObjDoc. "
			<< "Expected something castable to " 
			<< typeid( T ).name() 
			<< ", got object of class " 
			<< obj->getClassName() << " (according to AObject) " 
			<< typeid( *obj ).name() << "(according to C++ RTTI)."
		);
	
		throw "Unexpected type, failed to cast!";
	}
	
	// TODO: Keep a list of which objects were claimed how often for debug 
	//       builds so we can tell if we miss something
	
	return val;
}

#endif /*Inclusion Guard*/
